#include "CPUInfoBox.h"

#include "Bridge.h"
#include "Configuration.h"
#include "QBeaEngine.h"
#include "WordEditDialog.h"
#include "XrefBrowseDialog.h"

#if VMP_STUDIO
#include "VMPGlobal.h"

#define DbgGlobal VMPGlobal
#endif

CPUInfoBox::CPUInfoBox(QWidget* parent) : StdTable(parent) {
  setWindowTitle("InfoBox");
  enableMultiSelection(false);
  setShowHeader(false);
  addColumnAt(0, "", true);
  setRowCount(4);
  setCellContent(0, 0, "");
  setCellContent(1, 0, "");
  setCellContent(2, 0, "");
  setCellContent(3, 0, "");
  setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

  int height = getHeight();
  setMinimumHeight(height);

  connect(Bridge::getBridge(), SIGNAL(dbgStateChanged(DBGSTATE)), this,
          SLOT(dbgStateChanged(DBGSTATE)));
  connect(Bridge::getBridge(), SIGNAL(addMsgToLog(QByteArray)), this,
          SLOT(addMsgToLogSlot(QByteArray)));
  connect(Bridge::getBridge(), SIGNAL(addInfoLine(QString)), this,
          SLOT(addInfoLine(QString)));
  connect(this, SIGNAL(contextMenuSignal(QPoint)), this,
          SLOT(contextMenuSlot(QPoint)));
  connect(this, SIGNAL(doubleClickedSignal()), this, SLOT(doubleClickedSlot()));
  curAddr = 0;

  // Deselect any row (visual reasons only)
  setSingleSelection(-1);

  int maxModuleSize = (int)ConfigUint("Disassembler", "MaxModuleSize");
  mDisasm = new QBeaEngine(maxModuleSize);

  setupContextMenu();
}

CPUInfoBox::~CPUInfoBox() { delete mDisasm; }

void CPUInfoBox::setupContextMenu() {
  mCopyAddressAction = makeAction(tr("Address"), SLOT(copyAddress()));
  mCopyRvaAction = makeAction(tr("RVA"), SLOT(copyRva()));
  mCopyOffsetAction = makeAction(tr("File Offset"), SLOT(copyOffset()));
  mCopyLineAction = makeAction(tr("Copy Line"), SLOT(copyLineSlot()));
  setupShortcuts();
}

int CPUInfoBox::getHeight() { return ((getRowHeight() + 1) * 4); }

void CPUInfoBox::setInfoLine(int line, QString text) {
  if (line < 0 || line > 3) return;

  setCellContent(line, 0, text);
  reloadData();
}

QString CPUInfoBox::getInfoLine(int line) {
  if (line < 0 || line > 3) return QString();

  return getCellContent(line, 0);
}

void CPUInfoBox::addInfoLine(const QString& infoLine) {
  auto rowCount = getRowCount();
  setRowCount(rowCount + 1);
  setCellContent(rowCount, 0, infoLine);
  reloadData();
}

void CPUInfoBox::addMsgToLogSlot(const QByteArray& log) {
  const char* ptr = log.data() + log.length();
  int count = 0;
  for (; ptr > log.data(); ptr--) {
    if (*ptr == '\n') {
      if (++count > 5) {
        break;
      }
    }
  }
  QString tmplog(ptr[0] == '\n' ? ptr + 1 : ptr);
  QStringList tmplogs = tmplog.split('\n');
  int index = 3;
  for (auto l = tmplogs.rbegin(); l != tmplogs.rend(); l++) {
    if (l->length()) {
      setInfoLine(index, *l);
      index--;
    }
  }
}

void CPUInfoBox::clear() {
  // Set all 4 lines to empty strings
  setRowCount(4);
  setInfoLine(0, "");
  setInfoLine(1, "");
  setInfoLine(2, "");
  setInfoLine(3, "");
}

static QString formatSSEOperand(const QByteArray& data, uint8_t vectorType) {
  QString hex;
  bool isXMMdecoded = false;
  switch (vectorType) {
    case Zydis::VETFloat32:
      if (data.size() == 32) {
        hex = composeRegTextYMM(data.constData(), 1);
        isXMMdecoded = true;
      } else if (data.size() == 16) {
        hex = composeRegTextXMM(data.constData(), 1);
        isXMMdecoded = true;
      } else if (data.size() == 4) {
        hex = QString::number(((const float*)data.constData())[0]);
        isXMMdecoded = true;
      }
      break;
    case Zydis::VETFloat64:
      if (data.size() == 32) {
        hex = composeRegTextYMM(data.constData(), 2);
        isXMMdecoded = true;
      } else if (data.size() == 16) {
        hex = composeRegTextXMM(data.constData(), 2);
        isXMMdecoded = true;
      } else if (data.size() == 8) {
        hex = QString::number(((const double*)data.constData())[0]);
        isXMMdecoded = true;
      }
      break;
    default:
      isXMMdecoded = false;
      break;
  }
  if (!isXMMdecoded) {
    hex.reserve(data.size() * 3);
    for (int k = 0; k < data.size(); k++) {
      if (k) hex.append(' ');
      hex.append(ToByteString(data[k]));
    }
  }
  return hex;
}

void CPUInfoBox::disasmSelectionChanged(dsint parVA) {
  curAddr = parVA;
  curRva = -1;
  curOffset = -1;

  if (!DbgIsDebugging()
#if !VMP_STUDIO
      || !DbgMemIsValidReadPtr(parVA)
#endif
  )
    return;

#if !VMP_STUDIO
  // Rather than using clear() or setInfoLine(), only reset the first three
  // cells to reduce flicker
  setRowCount(4);
  setCellContent(0, 0, "");
  setCellContent(1, 0, "");
  setCellContent(2, 0, "");
#endif

  Instruction_t wInst;
  unsigned char instructiondata[MAX_DISASM_BUFFER];
  DbgMemRead(parVA, &instructiondata, MAX_DISASM_BUFFER);
  wInst = mDisasm->DisassembleAt(instructiondata, MAX_DISASM_BUFFER, 0,
                                 parVA - DbgGlobal::inst()->debugee->rtbase);
#if 0
    DISASM_INSTR instr; //Fix me: these disasm methods are so messy
    DbgDisasmAt(parVA, &instr);
    BASIC_INSTRUCTION_INFO basicinfo;
    DbgDisasmFastAt(parVA, &basicinfo);
#endif
  int start = 0;
#if !VMP_STUDIO
  bool commentThis = !ConfigBool("Disassembler", "OnlyCipAutoComments") ||
                     parVA == DbgValFromString("cip");
#else
  bool commentThis = false;
#endif
  if (wInst.branchType == Instruction_t::Conditional && commentThis)  // jump
  {
    bool taken = DbgIsJumpGoingToExecute(parVA);
    if (taken)
      setInfoLine(0, tr("Jump is taken"));
    else
      setInfoLine(0, tr("Jump is not taken"));
    start = 1;
  }

  bool bUpper = ConfigBool("Disassembler", "Uppercase");

#if 0
    for(int i = 0, j = start; i < instr.argcount && j < 2; i++)
    {
        DISASM_ARG arg = instr.arg[i];
        QString argMnemonic = QString(arg.mnemonic);
        if(bUpper)
            argMnemonic = argMnemonic.toUpper();
        if(arg.type == arg_memory)
        {
            bool ok;
            argMnemonic.toULongLong(&ok, 16);
            QString valText = DbgMemIsValidReadPtr(arg.value) ? ToPtrString(arg.value) : ToHexString(arg.value);
            auto valTextSym = getSymbolicNameStr(arg.value);
            if(!valTextSym.contains(valText))
                valText = QString("%1 %2").arg(valText, valTextSym);
            else
                valText = valTextSym;
            argMnemonic = !ok ? QString("%1]=[%2").arg(argMnemonic).arg(valText) : valText;
            QString sizeName = "";
            bool knownsize = true;
            switch(basicinfo.memory.size)
            {
            case size_byte:
                sizeName = "byte ptr ";
                break;
            case size_word:
                sizeName = "word ptr ";
                break;
            case size_dword:
                sizeName = "dword ptr ";
                break;
#ifdef _WIN64
            case size_qword:
                sizeName = "qword ptr ";
                break;
#endif  //_WIN64
            case size_xmmword:
                knownsize = false;
                sizeName = "xmmword ptr ";
                break;
            case size_ymmword:
                knownsize = false;
                sizeName = "ymmword ptr ";
                break;
            default:
                knownsize = false;
                break;
            }

            sizeName += [](SEGMENTREG seg)
            {
                switch(seg)
                {
                case SEG_ES:
                    return "es:";
                case SEG_DS:
                    return "ds:";
                case SEG_FS:
                    return "fs:";
                case SEG_GS:
                    return "gs:";
                case SEG_CS:
                    return "cs:";
                case SEG_SS:
                    return "ss:";
                default:
                    return "";
                }
            }(arg.segment);

            if(bUpper)
                sizeName = sizeName.toUpper();

            if(!DbgMemIsValidReadPtr(arg.value))
            {
                setInfoLine(j, sizeName + "[" + argMnemonic + "]=???");
            }
            else if(knownsize && wInst.vectorElementType[i] == Zydis::VETDefault) // MOVSD/MOVSS instruction
            {
                QString addrText = getSymbolicNameStr(arg.memvalue);
                setInfoLine(j, sizeName + "[" + argMnemonic + "]=" + addrText);
            }
            else
            {
                QByteArray data;
                data.resize(basicinfo.memory.size);
                memset(data.data(), 0, data.size());
                if(DbgMemRead(arg.value, data.data(), data.size()))
                {
                    setInfoLine(j, sizeName + "[" + argMnemonic + "]=" + formatSSEOperand(data, wInst.vectorElementType[i]));
                }
                else
                {
                    setInfoLine(j, sizeName + "[" + argMnemonic + "]=???");
                }
            }

            j++;
        }
        else
        {
            QString valText;
            auto symbolicName = getSymbolicNameStr(arg.value);
            if(!symbolicName.contains(valText))
                valText = QString("%1 (%2)").arg(symbolicName, valText);
            else
                valText = symbolicName;
            QString mnemonic(arg.mnemonic);
            bool ok;
            mnemonic.toULongLong(&ok, 16);
            if(ok) //skip certain numbers
            {
                if(ToHexString(arg.value) == symbolicName)
                    continue;
                setInfoLine(j, symbolicName);
            }
            else if(!mnemonic.startsWith("xmm") &&
                    !mnemonic.startsWith("ymm") &&
                    !mnemonic.startsWith("zmm") && //TODO: properly handle display of AVX-512 registers
                    !mnemonic.startsWith("k") && //TODO: properly handle display of AVX-512 registers
                    !mnemonic.startsWith("st"))
            {
                setInfoLine(j, mnemonic + "=" + valText);
                j++;
            }
#if 0
            else if(mnemonic.startsWith("xmm") || mnemonic.startsWith("ymm") || mnemonic.startsWith("st"))
            {
                REGDUMP registers;
                DbgGetRegDumpEx(&registers, sizeof(registers));
                if(mnemonic == "xmm0")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[0], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm1")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[1], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm2")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[2], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm3")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[3], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm4")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[4], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm5")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[5], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm6")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[6], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm7")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[7], 16), wInst.vectorElementType[i]);
#ifdef _WIN64
                else if(mnemonic == "xmm8")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[8], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm9")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[9], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm10")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[10], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm11")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[11], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm12")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[12], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm13")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[13], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm14")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[14], 16), wInst.vectorElementType[i]);
                else if(mnemonic == "xmm15")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.XmmRegisters[15], 16), wInst.vectorElementType[i]);
#endif  //_WIN64
                else if(mnemonic == "ymm0")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[0], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm1")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[1], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm2")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[2], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm3")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[3], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm4")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[4], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm5")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[5], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm6")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[6], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm7")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[7], 32), wInst.vectorElementType[i]);
#ifdef _WIN64
                else if(mnemonic == "ymm8")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[8], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm9")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[9], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm10")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[10], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm11")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[11], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm12")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[12], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm13")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[13], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm14")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[14], 32), wInst.vectorElementType[i]);
                else if(mnemonic == "ymm15")
                    valText = formatSSEOperand(QByteArray((const char*)&registers.regcontext.YmmRegisters[15], 32), wInst.vectorElementType[i]);
#endif  //_WIN64
                else if(mnemonic == "st0")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[registers.x87StatusWordFields.TOP & 7]);
                else if(mnemonic == "st1")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 1) & 7]);
                else if(mnemonic == "st2")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 2) & 7]);
                else if(mnemonic == "st3")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 3) & 7]);
                else if(mnemonic == "st4")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 4) & 7]);
                else if(mnemonic == "st5")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 5) & 7]);
                else if(mnemonic == "st6")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 6) & 7]);
                else if(mnemonic == "st7")
                    valText = ToLongDoubleString(&registers.x87FPURegisters[(registers.x87StatusWordFields.TOP + 7) & 7]);
                setInfoLine(j, mnemonic + "=" + valText);
                j++;
            }
#endif
        }
    }
#endif

#if VMP_STUDIO
  auto mdb = DbgGlobal::inst()->debugee;
  auto func = mdb->rvaToFunction(parVA - mdb->rtbase, false);
  if (func) {
    auto sect = mdb->rvaToSection(parVA - mdb->rtbase);
    const char *sectname = sect ? sect->name.data() : "unknown_sect";
    setInfoLine(3, QString("%1.%2 %3-%4-%5 %6")
                       .arg(sectname)
                       .arg(func.func->name.data())
                       .arg(ToPtrString(mdb->rtbase + func.rvastart))
                       .arg(ToPtrString(parVA))
                       .arg(ToPtrString(mdb->rtbase + func.rvaend))
                       .arg(func.rvaend - func.rvastart));
  }
#else
  if (getInfoLine(0) == getInfoLine(1))  // check for duplicate info line
    setInfoLine(1, "");

  // check references details
  // code extracted from ExtraInfo plugin by torusrxxx
  XREF_INFO xrefInfo;
  xrefInfo.refcount = 0;
  xrefInfo.references = nullptr;

  if (DbgXrefGet(parVA, &xrefInfo) && xrefInfo.refcount > 0) {
    QString output;
    std::vector<XREF_RECORD*> data;
    for (duint i = 0; i < xrefInfo.refcount; i++)
      data.push_back(&xrefInfo.references[i]);

    std::sort(data.begin(), data.end(),
              [](const XREF_RECORD* A, const XREF_RECORD* B) {
                if (A->type != B->type) return (A->type < B->type);

                return (A->addr < B->addr);
              });

    int t = XREF_NONE;
    duint i;

    for (i = 0; i < xrefInfo.refcount && i < 10; i++) {
      if (t != data[i]->type) {
        switch (data[i]->type) {
          case XREF_JMP:
            output += tr("Jump from ");
            break;
          case XREF_CALL:
            output += tr("Call from ");
            break;
          default:
            output += tr("Reference from ");
            break;
        }

        t = data[i]->type;
      }

      char clabel[MAX_LABEL_SIZE] = "";

      DbgGetLabelAt(data[i]->addr, SEG_DEFAULT, clabel);
      if (*clabel)
        output += QString(clabel);
      else {
        duint start;
        if (DbgFunctionGet(data[i]->addr, &start, nullptr) &&
            DbgGetLabelAt(start, SEG_DEFAULT, clabel) && start != data[i]->addr)
          output += QString("%1+%2").arg(clabel).arg(
              ToHexString(data[i]->addr - start));
        else
          output += QString("%1").arg(ToHexString(data[i]->addr));
      }

      if (i != xrefInfo.refcount - 1) output += ", ";
    }

    data.clear();
    if (xrefInfo.refcount > 10) output += "...";

    setInfoLine(2, output);
  }

  // Set last line
  //
  // Format: SECTION:VA MODULE:$RVA :#FILE_OFFSET FUNCTION, Accessed %u times
  QString info;

  // Section
  char section[MAX_SECTION_SIZE * 5];
  if (DbgFunctions()->SectionFromAddr(parVA, section))
    info += QString(section) + ":";

  // VA
  info += ToPtrString(parVA);

  // Module name, RVA, and file offset
  char mod[MAX_MODULE_SIZE];
  if (DbgFunctions()->ModNameFromAddr(parVA, mod, true)) {
    dsint modbase = DbgFunctions()->ModBaseFromAddr(parVA);

    // Append modname
    info += " " + QString(mod);

    // Module RVA
    curRva = parVA - modbase;
    if (modbase) info += QString(":$%1").arg(ToHexString(curRva));

    // File offset
    curOffset = DbgFunctions()->VaToFileOffset(parVA);
    info += QString(" #%1").arg(ToHexString(curOffset));
  }

  // Function/label name
  char label[MAX_LABEL_SIZE];
  if (DbgGetLabelAt(parVA, SEG_DEFAULT, label))
    info += QString(" <%1>").arg(label);
  else {
    duint start;
    if (DbgFunctionGet(parVA, &start, nullptr) &&
        DbgGetLabelAt(start, SEG_DEFAULT, label) && start != parVA)
      info += QString(" <%1+%2>").arg(label).arg(ToHexString(parVA - start));
  }

  auto tracedCount = DbgFunctions()->GetTraceRecordHitCount(parVA);
  if (tracedCount != 0) {
    info += ", " + tr("Accessed %n time(s)", nullptr, tracedCount);
  }

  setInfoLine(3, info);

  DbgSelChanged(GUI_DISASSEMBLY, parVA);
#endif
}

void CPUInfoBox::dbgStateChanged(DBGSTATE state) {
  if (state == stopped) clear();
}

/**
 * @brief CPUInfoBox::followActionSlot Called when follow or watch action is
 * clicked
 */
void CPUInfoBox::followActionSlot() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (action && action->objectName().startsWith("DUMP|"))
    DbgCmdExec(QString("dump \"%1\"").arg(action->objectName().mid(5)));
  else if (action && action->objectName().startsWith("WATCH|"))
    DbgCmdExec(QString("AddWatch %1").arg(action->objectName().mid(6)));
}

void CPUInfoBox::modifySlot() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (action) {
    duint addrVal = 0;
    DbgFunctions()->ValFromString(action->objectName().toUtf8().constData(),
                                  &addrVal);
    WordEditDialog wEditDialog(this);
    dsint value = 0;
    DbgMemRead(addrVal, &value, sizeof(dsint));
    wEditDialog.setup(tr("Modify Value"), value, sizeof(dsint));
    if (wEditDialog.exec() != QDialog::Accepted) return;
    value = wEditDialog.getVal();
    DbgMemWrite(addrVal, &value, sizeof(dsint));
    GuiUpdateAllViews();
  }
}

void CPUInfoBox::findXReferencesSlot() {
  if (!DbgIsDebugging()) return;
  if (!mXrefDlg) mXrefDlg = new XrefBrowseDialog(this);
  mXrefDlg->setup(curAddr, [](duint address) {
    DbgCmdExec(QString("disasm %1").arg(ToPtrString(address)));
  });
  mXrefDlg->showNormal();
}

void CPUInfoBox::addModifyValueMenuItem(QMenu* menu, QString name,
                                        duint value) {
  foreach (QAction* action, menu->actions())  // check for duplicate action
    if (action->text() == name) return;
  QAction* newAction = new QAction(name, menu);
  menu->addAction(newAction);
  newAction->setObjectName(ToPtrString(value));
  connect(newAction, SIGNAL(triggered()), this, SLOT(modifySlot()));
}

void CPUInfoBox::setupModifyValueMenu(QMenu* menu, duint wVA) {
  menu->setIcon(DIcon("modify.png"));

  // add follow actions
  DISASM_INSTR instr;
  DbgDisasmAt(wVA, &instr);

  for (int i = 0; i < instr.argcount; i++) {
    const DISASM_ARG arg = instr.arg[i];
    if (arg.type == arg_memory) {
      QString segment = "";
#ifdef _WIN64
      if (arg.segment == SEG_GS) segment = "gs:";
#else   // x32
      if (arg.segment == SEG_FS) segment = "fs:";
#endif  //_WIN64
      if (DbgMemIsValidReadPtr(arg.value))
        addModifyValueMenuItem(menu,
                               tr("&Address: ") + segment +
                                   QString(arg.mnemonic).toUpper().trimmed(),
                               arg.value);
      if (arg.value != arg.constant) {
        QString constant = QString("%1").arg(ToHexString(arg.constant));
        if (DbgMemIsValidReadPtr(arg.constant))
          addModifyValueMenuItem(menu, tr("&Constant: ") + constant,
                                 arg.constant);
      }
      if (DbgMemIsValidReadPtr(arg.memvalue))
        addModifyValueMenuItem(menu,
                               tr("&Value: ") + segment + "[" +
                                   QString(arg.mnemonic).toUpper().trimmed() +
                                   "]",
                               arg.memvalue);
    } else {
      if (DbgMemIsValidReadPtr(arg.value))
        addModifyValueMenuItem(
            menu, "&Value: [" + QString(arg.mnemonic).toUpper().trimmed() + "]",
            arg.value);
    }
  }
}

/**
 * @brief CPUInfoBox::addFollowMenuItem Add a follow action to the menu
 * @param menu The menu to which the follow action adds
 * @param name The user-friendly name of the action
 * @param value The VA of the address
 */
void CPUInfoBox::addFollowMenuItem(QMenu* menu, QString name, duint value) {
  foreach (QAction* action, menu->actions())  // check for duplicate action
    if (action->text() == name) return;
  QAction* newAction = new QAction(name, menu);
  menu->addAction(newAction);
  newAction->setObjectName(QString("DUMP|") + ToPtrString(value));
  connect(newAction, SIGNAL(triggered()), this, SLOT(followActionSlot()));
}

/**
 * @brief CPUInfoBox::setupFollowMenu Set up a follow menu.
 * @param menu The menu to create
 * @param wVA The selected VA
 */
void CPUInfoBox::setupFollowMenu(QMenu* menu, duint wVA) {
  menu->setIcon(DIcon("dump.png"));
  // most basic follow action
  addFollowMenuItem(menu, tr("&Selected Address"), wVA);

  // add follow actions
  DISASM_INSTR instr;
  DbgDisasmAt(wVA, &instr);

  for (int i = 0; i < instr.argcount; i++) {
    const DISASM_ARG arg = instr.arg[i];
    if (arg.type == arg_memory) {
      QString segment = "";
#ifdef _WIN64
      if (arg.segment == SEG_GS) segment = "gs:";
#else   // x32
      if (arg.segment == SEG_FS) segment = "fs:";
#endif  //_WIN64
      if (DbgMemIsValidReadPtr(arg.value))
        addFollowMenuItem(menu,
                          tr("&Address: ") + segment +
                              QString(arg.mnemonic).toUpper().trimmed(),
                          arg.value);
      if (arg.value != arg.constant) {
        QString constant = QString("%1").arg(ToHexString(arg.constant));
        if (DbgMemIsValidReadPtr(arg.constant))
          addFollowMenuItem(menu, tr("&Constant: ") + constant, arg.constant);
      }
      if (DbgMemIsValidReadPtr(arg.memvalue))
        addFollowMenuItem(
            menu, tr("&Value: ") + segment + "[" + QString(arg.mnemonic) + "]",
            arg.memvalue);
    } else {
      if (DbgMemIsValidReadPtr(arg.value))
        addFollowMenuItem(menu, QString(arg.mnemonic).toUpper().trimmed(),
                          arg.value);
    }
  }
}

/**
 * @brief CPUInfoBox::addFollowMenuItem Add a follow action to the menu
 * @param menu The menu to which the follow action adds
 * @param name The user-friendly name of the action
 * @param value The VA of the address
 */
void CPUInfoBox::addWatchMenuItem(QMenu* menu, QString name, duint value) {
  foreach (QAction* action, menu->actions())  // check for duplicate action
    if (action->text() == name) return;
  QAction* newAction = new QAction(name, menu);
  menu->addAction(newAction);
  newAction->setObjectName(QString("WATCH|") + ToPtrString(value));
  connect(newAction, SIGNAL(triggered()), this, SLOT(followActionSlot()));
}

/**
 * @brief CPUInfoBox::setupFollowMenu Set up a follow menu.
 * @param menu The menu to create
 * @param wVA The selected VA
 */
void CPUInfoBox::setupWatchMenu(QMenu* menu, duint wVA) {
  menu->setIcon(DIcon("animal-dog.png"));
  // most basic follow action
  addWatchMenuItem(menu, tr("&Selected Address"), wVA);

  // add follow actions
  DISASM_INSTR instr;
  DbgDisasmAt(wVA, &instr);

  for (int i = 0; i < instr.argcount; i++) {
    const DISASM_ARG arg = instr.arg[i];
    if (arg.type == arg_memory) {
      QString segment = "";
#ifdef _WIN64
      if (arg.segment == SEG_GS) segment = "gs:";
#else   // x32
      if (arg.segment == SEG_FS) segment = "fs:";
#endif  //_WIN64
      if (DbgMemIsValidReadPtr(arg.value))
        addWatchMenuItem(menu,
                         tr("&Address: ") + segment +
                             QString(arg.mnemonic).toUpper().trimmed(),
                         arg.value);
      if (arg.value != arg.constant) {
        QString constant = QString("%1").arg(ToHexString(arg.constant));
        if (DbgMemIsValidReadPtr(arg.constant))
          addWatchMenuItem(menu, tr("&Constant: ") + constant, arg.constant);
      }
      if (DbgMemIsValidReadPtr(arg.memvalue))
        addWatchMenuItem(
            menu, tr("&Value: ") + segment + "[" + QString(arg.mnemonic) + "]",
            arg.memvalue);
    } else {
      if (DbgMemIsValidReadPtr(arg.value))
        addWatchMenuItem(menu, QString(arg.mnemonic).toUpper().trimmed(),
                         arg.value);
    }
  }
}

int CPUInfoBox::followInDump(dsint wVA) {
  // Copy pasta from setupFollowMenu for now
  int tableOffset = getInitialSelection();
  QString cellContent = this->getCellContent(tableOffset, 0);

  // No text in row that was clicked
  if (cellContent.length() == 0) return -1;

  // Last line of infoBox => Current Address(EIP) in disassembly
  if (tableOffset == 2) {
    DbgCmdExec(QString("dump %1").arg(ToPtrString(wVA)));
    return 0;
  }

  DISASM_INSTR instr;
  DbgDisasmAt(wVA, &instr);

  if (instr.type == instr_branch && cellContent.contains("Jump")) {
    DbgCmdExec(QString("dump %1").arg(ToPtrString(instr.arg[0].value)));
    return 0;
  }

  // Loop through all instruction arguments
  for (int i = 0; i < instr.argcount; i++) {
    const DISASM_ARG arg = instr.arg[i];
    if (arg.type == arg_memory) {
      if (DbgMemIsValidReadPtr(arg.value)) {
        if (cellContent.contains(arg.mnemonic)) {
          DbgCmdExec(QString("dump %1").arg(ToPtrString(arg.value)));
          return 0;
        }
      }
    }
  }
  return 0;
}

void CPUInfoBox::contextMenuSlot(QPoint pos) {
  if (!DbgIsDebugging()) return;
  QMenu wMenu(this);  // create context menu
#if 0
  QMenu wFollowMenu(tr("&Follow in Dump"), this);
  setupFollowMenu(&wFollowMenu, curAddr);
  wMenu.addMenu(&wFollowMenu);
  QMenu wModifyValueMenu(tr("&Modify Value"), this);
  setupModifyValueMenu(&wModifyValueMenu, curAddr);
  if (!wModifyValueMenu.isEmpty()) wMenu.addMenu(&wModifyValueMenu);
  QMenu wWatchMenu(tr("&Watch"), this);
  setupWatchMenu(&wWatchMenu, curAddr);
  wMenu.addMenu(&wWatchMenu);
  if (!getInfoLine(2).isEmpty())
    wMenu.addAction(makeAction(DIcon("xrefs.png"), tr("&Show References"),
                               SLOT(findXReferencesSlot())));
#endif
  QMenu wCopyMenu(tr("&Copy"), this);
  setupCopyMenu(&wCopyMenu);
  if (DbgIsDebugging()) {
    wCopyMenu.addAction(mCopyAddressAction);
    if (curRva != -1) wCopyMenu.addAction(mCopyRvaAction);
    if (curOffset != -1) wCopyMenu.addAction(mCopyOffsetAction);
  }
  if (wCopyMenu.actions().length()) {
    wMenu.addSeparator();
    wMenu.addMenu(&wCopyMenu);
  }
  wMenu.exec(mapToGlobal(pos));  // execute context menu
}

void CPUInfoBox::copyAddress() {
  Bridge::CopyToClipboard(ToPtrString(curAddr));
}

void CPUInfoBox::copyRva() { Bridge::CopyToClipboard(ToHexString(curRva)); }

void CPUInfoBox::copyOffset() {
  Bridge::CopyToClipboard(ToHexString(curOffset));
}

void CPUInfoBox::doubleClickedSlot() { followInDump(curAddr); }

void CPUInfoBox::setupShortcuts() {
  mCopyLineAction->setShortcut(ConfigShortcut("ActionCopyLine"));
  addAction(mCopyLineAction);
}
